#pragma once

#include <unordered_map>
#include <list>
#include <typeindex>
#include <type_traits>

#define SetEventBusEvent(name) class name: public NahidaProject::EventBus::EventBusEvent { public: name() : NahidaProject::EventBus::EventBusEvent(nullptr) { } }

namespace NahidaProject {
	namespace EventBus {
		class __declspec(dllexport) EventBusEvent;

		template<class T> class __declspec(dllexport) EventBusHandler {
		private:
			friend class EventBus;
			void Dispatch(const EventBusEvent& e) {
				OnEvent(reinterpret_cast<const T&>(e));
			}
		public:
			EventBusHandler() {
				static_assert(std::is_base_of<EventBusEvent, T>::value, "Wrong type given");
			}

			virtual ~EventBusHandler() {
			}

			virtual void OnEvent(const T&) = 0;
		};


		class __declspec(dllexport) EventBusTopic {
		protected:
			const std::type_index _index;

			EventBusTopic() :
				_index(typeid(*this)) {
			}
		public:
			virtual ~EventBusTopic() {

			}

			inline bool operator==(const EventBusTopic& other) const;
		};

		class __declspec(dllexport) EventBusEvent {
		protected:
			const EventBusTopic* _topic;

			EventBusEvent(const EventBusTopic* const topic = nullptr) :
				_topic(topic) {
			}
		private:
			friend class __declspec(dllexport) EventBus;
			inline const EventBusTopic* GetTopic() const {
				return _topic;
			}
		public:

			virtual ~EventBusEvent() {
			}
		};

		class __declspec(dllexport) EventBus {
		private:
			class __declspec(dllexport) EventBusHandlerReference;
			typedef std::list<EventBusHandlerReference> EventBusHandlerReferences;
			typedef std::unordered_map<std::type_index, EventBusHandlerReferences> EventBusHandlerReferenceMap;

			class __declspec(dllexport) EventBusHandlerReference {
			private:
				void* const _handler;
				const EventBusTopic* _topic;

			public:
				EventBusHandlerReference(void* handler, const EventBusTopic* topic) :
					_handler(handler), _topic(topic) {
				}

				EventBusHandlerReference operator=(const EventBusHandlerReference&) {
					EventBusHandlerReference eventBusHandlerReference(0, 0);
					return eventBusHandlerReference;

				}

				inline EventBusHandler<EventBusEvent>* GetHandler() const;
				inline const EventBusTopic* GetTopic() const;
			};

			EventBusHandlerReferenceMap _handlers;

		public:
			template<class T> void Subscribe(EventBusHandler<T>& handler, const EventBusTopic* topic = nullptr) {
				const std::type_index& index = typeid(T);
				EventBusHandlerReferences& handlers = _handlers[index];
				const EventBusHandlerReference registration(&handler, topic);
				handlers.push_back(registration);
			}

			template<class T> int Unsubscribe(EventBusHandler<T>& handler, const EventBusTopic* topic = nullptr) {
				int unsubscribedHandlers = 0;
				const std::type_index& index = typeid(T);
				EventBusHandlerReferences& handlers = _handlers[index];
				for (EventBusHandlerReferences::iterator i = handlers.begin(); i != handlers.end();) {
					EventBusHandlerReference& r = *i;
					if (r.GetHandler() != reinterpret_cast<EventBusHandler<EventBusEvent>*>(&handler)) {
						++i;
						continue;
					}
					if (topic != nullptr) {
						if (r.GetTopic() == nullptr) {
							++i;
							continue;
						}
						if (!(*r.GetTopic() == *topic)) {
							++i;
							continue;
						}
					}
					i = handlers.erase(i);
					++unsubscribedHandlers;
				}
				return unsubscribedHandlers;
			}

			int Publish(const EventBusEvent& e);
		};
	}
}
